import os, random

import lib, sql

from xdh_logging import initLog
from nothing import Nothing
from settings import settings
from track import Track
from cache import cache

log = initLog("holiday_catfile")

class HolidayCatFile(object):
	"""
	Class that manages a specific category file that contains all of the tracks within a single category.
	Designed to use the with statement, and will automatically save a file if the dirty flag is set to true.

	This class has a number of features that makes use of certain convenient magic methods. It more or less
	functions like a set, and the full API for a set has been replicated in it. It also supports adding
	and removing tracks through the plus and minus operator.

	This object is hashable, based on the cat read-only attribute.

	The dirty read-only attribute is used to determine if/when the file has changed and if it needs to be saved.

	"""

	def __init__(self, holiday, favorites_only = False, test = False, data = None):
		"""
		Constructs the HolidayCatFile instance, loading a category file if it exists.
		If the optional value of load is set to False, no actual datafile will be loaded.
		The optional value of path is used primarily for testing purposes. The better place
		to configure the path value is within the settings.yaml file.
		The optional value of test is used to determine if this is to be a test or not.
		"""

		self._holiday = holiday

		self._test = test

		if data != None:
			self.data = set(data)
		else:
			log.info("<{holiday}> Loading tracks into holiday catfile.".format(holiday = holiday))
			self.data = {Track(track) for track in sql.TrackFlagData.get_tracks(self.holiday, favorites_only)}

			log.info("<{holiday}> Loaded {count} track{plural} into holiday catfile.".format(holiday = self.holiday, count = len(self.data), plural = ("s" if len(self.data) != 1 else "")))


	def union(self, data):
		"""
		Performs a union of this HolidayCatFile's tracks with the given data.  The data can be another set,
		a frozenset, a list, or a tuple of tracks (or simply track filenames), or it can be another
		HolidayCatFile instance.
		"""

		if isinstance(data, HolidayCatFile):
			return self.data | data.data
		else:
			return self.data | self._convert(data)


	def intersection(self, data):
		"""
		Performs an intersection of this HolidayCatFile's tracks with the given data.  The data can be
		another set, a frozenset, a list, or a tuple of tracks (or simply track filenames), or it
		can be another HolidayCatFile instance.
		"""

		if isinstance(data, HolidayCatFile):
			return self.data & data.data
		else:
			return self.data & self._convert(data)


	def difference(self, data):
		"""
		Performs an difference of this HolidayCatFile's tracks with the given data.  The data can be
		another set, a frozenset, a list, or a tuple of tracks (or simply track filenames), or it
		can be another HolidayCatFile instance.
		"""

		if isinstance(data, HolidayCatFile):
			return self.data - data.data
		else:
			return self.data - self._convert(data)


	def symmetric_difference(self, data):
		"""
		Performs a symmetric difference of this HolidayCatFile's tracks with the given data.  The data can
		be another set, a frozenset, a list, or a tuple of tracks (or simply track filenames), or it
		can be another HolidayCatFile instance.
		"""

		if isinstance(data, HolidayCatFile):
			return self.data ^ data.data
		else:
			return self.data ^ self._convert(data)


	def add(self, track):
		"""
		Adds a track to the HolidayCatFile. The parameter can be either a filename or a Track instance.
		"""

		track = lib.fix_filename(track, reverse = True)

		if track not in self.data:
			self._dirty = True

			self.data.add(track)

		return self
			

	def remove(self, track):
		"""
		Deletes a track from the HolidayCatFile. The parameter can be either a filename or a Track instance.
		"""
		track = lib.fix_filename(track, reverse = True)

		if track in self.data:
			self._dirty = True

			self.data.remove(track)

		return self


	@property
	def holiday(self):
		"""
		Returns the category (cat) for this HolidayCatFile.
		"""
		if hasattr(self, "_holiday"):
			return self._holiday
		else:
			return "<Unknown>"


	def _convert(self, data):
		return {lib.fix_filename(track, reverse = True) for track in data}

	def __ior__(self, data):
		"""
		Performs a union.
		"""
		if isinstance(data, HolidayCatFile):
			self.data |= data.data
		else:
			self.data |= self._convert(data)

		self._dirty = True

		return self


	def __iadd__(self, track):
		"""
		Updates/Adds a track.
		"""
		self.add(track)

		self._dirty = True

		return self


	def __isub__(self, data):
		"""
		Either removes a track (if the parameter is a track or a filename) or performs a difference
		to the HolidayCatFile.
		"""
		if not isinstance(data, Track) and not isinstance(data, basestring) and (hasattr(data, "__getitem__") or (hasattr(data, "union") and hasattr(data, "isdisjoint"))):
			if isinstance(data, HolidayCatFile):
				self.data -= data.data
			else:
				self.data -= self._convert(data)

			self._dirty = True

		elif data != None:
			self.remove(data)

		return self


	def __iand__(self, data):
		"""
		Performs an intersection.
		"""
		if isinstance(data, HolidayCatFile):
			self.data &= data.data
		else:
			self.data &= self._convert(data)

		self._dirty = True

		return self


	def __ixor__(self, data):
		"""
		Performs a symmetric difference.
		"""
		if isinstance(data, HolidayCatFile):
			self.data ^= data.data
		else:
			self.data ^= self._convert(data)

		self._dirty = True

		return self


	def copy(self):
		"""
		Makes a shallow copy of the HolidayCatFile.
		"""
		ret = HolidayCatFile(holiday = self.holiday, test = self._test, data = self.data)

		return ret


	def __or__(self, data):
		"""
		Returns a copy of the HolidayCatFile that has been unioned with the given value.
		"""
		return self.union(data)


	def __add__(self, track):
		"""
		Returns a copy of the HolidayCatFile that has had the given track added to it.
		"""

		ret = self.copy()

		ret += track

		return ret


	def __sub__(self, data):
		"""
		Returns a copy of the HolidayCatFile that either has had the given track removed from it or
		has been differenced with the given data set.
		"""
		if not isinstance(data, Track) and not isinstance(data, basestring) and (hasattr(data, "__getitem__") or (hasattr(data, "union") and hasattr(data, "isdisjoint"))):
			return self.difference(data)

		elif data != None:
			ret = self.copy()

			ret.remove(data)

			return ret


	def __and__(self, data):
		"""
		Returns a copy of the HolidayCatFile that has been intersected with the given value.
		"""
		return self.intersection(data)


	def __xor__(self, data):
		"""
		Returns a copy of the HolidayCatFile that has been symmetrically differenced with the given value.
		"""
		return self.symmetric_difference(data)

	
	def __ror__(self, data):
		"""
		Right-sided equivalent of __or__()
		"""
		if isinstance(data, HolidayCatFile):
			ret = data.copy()
			ret |= self.data
			return ret
		else:
			return self._convert(data) | self.data


	def __radd__(self, track):
		"""
		Right-sided equivalent of __add__()
		"""
		return self + track

	def __rsub__(self, data):
		"""
		Right-sided equivalent of __sub__()
		"""
		if not isinstance(data, Track) and not isinstance(data, basestring) and (hasattr(data, "__getitem__") or (hasattr(data, "union") and hasattr(data, "isdisjoint"))):
			if isinstance(data, HolidayCatFile):
				ret = data.copy()
				ret -= self.data
				return ret
			else:
				return self._convert(data) - self.data

		else:
			ret = self.copy()

			ret.remove(data)

			return ret


	def __rand__(self, data):
		"""
		Right-sided equivalent of __and__()
		"""
		if isinstance(data, HolidayCatFile):
			ret = data.copy()
			ret &= self.data
			return ret
		else:
			return self._convert(data) & self.data


	def __rxor__(self, data):
		"""
		Right-sided equivalent of __xor__()
		"""
		if isinstance(data, HolidayCatFile):
			ret = data.copy()
			ret ^= self.data
			return ret
		else:
			return self._convert(data) ^ self.data


	def __iter__(self):
		"""
		Returns an iterator for this HolidayCatFile.
		"""

		for track in self.data:
			yield track


	def issubset(self, other):
		"""
		Returns whether this HolidayCatFile is a subset of the given value.
		"""
		if isinstance(other, HolidayCatFile):
			return self.data.issubset(other.data)
		else:
			return self.data.issubset(self._convert(other))


	def isdisjoint(self, other):
		"""
		Returns whether this HolidayCatFile is disjointed (shares no common elements with) the given value.
		"""
		if isinstance(other, HolidayCatFile):
			return self.data.isdisjoint(other.data)
		else:
			return self.data.isdisjoint(self._convert(other))


	def issuperset(self, other):
		"""
		Returns whether this HolidayCatFile is a superset of the given value.
		"""
		if isinstance(other, HolidayCatFile):
			return self.data.issuperset(other.data)
		else:
			return self.data.issuperset(self._convert(other))

	
	def __ne__(self, other):
		"""
		Returns whether this HolidayCatFile is not equal to the given value.
		"""
		
		if isinstance(other, HolidayCatFile):
			return self.data != other.data
		else:
			return self.data != self._convert(other)


	def __eq__(self, other):
		"""
		Returns whether this HolidayCatFile is equal to the given value.
		"""
		if isinstance(other, HolidayCatFile):
			return self.data == other.data
		else:
			return self.data == self._convert(other)


	def __le__(self, other):
		"""
		Returns whether this HolidayCatFile is a subset of the given value.
		"""
		return self.issubset(other)


	def __ge__(self, other):
		"""
		Returns whether this HolidayCatFile is a superset of the given value.
		"""
		return self.issuperset(other)


	def __lt__(self, other):
		"""
		Returns whether this HolidayCatFile is a true subset of the given value. Equivalent to: self <= other and self != other.
		"""
		return self <= other and self != other


	def __gt__(self, other):
		"""
		Returns whether this HolidayCatFile is a true superset of the given value. Equivalent to: self >= other and self != other.
		"""
		return self >= other and self != other


	def __len__(self):
		"""
		Returns the length of this HolidayCatFile
		"""
		return len(self.data)


	def __contains__(self, track):
		"""
		Tests if this HolidayCatFile contains the given track. Can be used for either a filename or a Track object.
		"""
		return lib.fix_filename(track, reverse = True) in self.data


	def __hash__(self):
		"""
		Returns the hash of this HolidayCatFile, based on the cat property.
		"""
		return self.holiday.__hash__()


	def __repr__(self):
		"""
		Returns a string representation of the set of tracks in the HolidayCatFile.
		"""
		if hasattr(self, "data"):
			return repr(self.data)
		else:
			return repr(set())


	def __str__(self):
		return lib.safe_str(unicode(self))


	def __unicode__(self):
		"""
		Returns a more detailed informative line for this HolidayCatFile.
		"""

		return u"HolidayCatFile: [{holiday}] ({num_tracks} track{plural})".format(holiday = lib.safe_unicode(self.holiday), num_tracks = len(self), plural = "s" if len(self) != 1 else "")

	@classmethod
	def getHolidayList(cls):
		"""
		Gets the list of existing categories based on what categories are defined for the tracks in the database.
		"""

		return list({holiday for holiday in sql.TrackFlagData.gen_holidays()})

